home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / bin / gnome-about < prev    next >
Text File  |  2009-11-03  |  37KB  |  1,046 lines

  1. #!/usr/bin/python
  2. # coding=utf-8
  3.  
  4. '''
  5. gnome-about
  6.  
  7.  # Pretty About Dialog for the GNOME Desktop #
  8.  
  9. Copyright (C) 2007 Guillaume Seguin <guillaume@segu.in>
  10.  
  11. This program is free software; you can redistribute it and/or
  12. modify it under the terms of the GNU General Public License
  13. as published by the Free Software Foundation; either version 2
  14. of the License, or (at your option) any later version.
  15.  
  16. This program is distributed in the hope that it will be useful,
  17. but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  19. GNU General Public License for more details.
  20.  
  21. You should have received a copy of the GNU General Public License
  22. along with this program; if not, write to the Free Software
  23. Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
  24.  
  25. Authors:
  26.     Guillaume Seguin <guillaume@segu.in>
  27.     Vincent Untz <vuntz@gnome.org> (get_language_names () helper function)
  28. '''
  29.  
  30. import pygtk
  31. pygtk.require ('2.0')
  32.  
  33. import gobject
  34. from gobject.option import OptionParser, make_option
  35. import gtk
  36.  
  37. import cairo
  38. from math import pi
  39.  
  40. import os, sys, random, time, gettext, locale
  41.  
  42. import xml.dom.minidom
  43.  
  44. DESCRIPTION_DELAY = 10000
  45. CONTRIBUTOR_DELAY = 2500
  46.  
  47. PACKAGE         = "gnome-desktop"
  48. VERSION         = "2.28.1"
  49. GETTEXT_PACKAGE = "gnome-desktop-2.0"
  50.  
  51. LOCALEDIR       = "/usr/share/locale"
  52. DATADIR         = "/usr/share/gnome-about"
  53. ICONDIR         = "/usr/share/pixmaps"
  54.  
  55. LOGO_FILE        = "gnome-64.png"
  56.  
  57. gettext.install (GETTEXT_PACKAGE, LOCALEDIR, unicode = True)
  58.  
  59. header_links = [
  60.     (_("About GNOME"), "http://www.gnome.org/about/"),
  61.     (_("News"), "http://news.gnome.org/"),
  62.     (_("GNOME Library"), "http://library.gnome.org/"),
  63.     (_("Friends of GNOME"), "http://www.gnome.org/friends/"),
  64.     (_("Contact"), "http://www.gnome.org/contact/"),
  65. ]
  66.  
  67. translated_contributors = [
  68.     _("The Mysterious GEGL"),
  69.     _("The Squeaky Rubber GNOME"),
  70.     _("Wanda The GNOME Fish")
  71. ]
  72.  
  73. default_link_color = gtk.gdk.Color (0, 0, 65535)
  74.  
  75. def locate_file (file):
  76.     '''Wrap gnome_program_locate_file to avoid ugly duplication'''
  77.     dirnames = [DATADIR, '.']
  78.     for dirname in dirnames:
  79.         filename = os.path.join(dirname, file)
  80.         if os.path.exists(filename):
  81.             return filename
  82.     return False
  83.  
  84. def cleanup_date (date):
  85.     '''Parse a date as found in gnome-version.xml and nicely format it'''
  86.     try:
  87.         # FIXME: we don't have g_locale_to_utf8 in python. See bug #530382
  88.         return unicode (time.strftime ("%x", time.strptime (date, "%Y-%m-%d")), locale.getpreferredencoding ()).encode ("utf-8")
  89.     except:
  90.         return ""
  91.  
  92. # Imported from GNOME's Sabayon
  93. # (sabayon/admin-tool/lockdown/disabledapplets.py)
  94. # There's no wrapper for g_get_language_names (). Ugly workaround:
  95. # Note that we don't handle locale alias...
  96. def get_language_names ():
  97.     if "LANGUAGE" in os.environ.keys () and os.environ["LANGUAGE"] != "":
  98.         env_lang = os.environ["LANGUAGE"].split ()
  99.     elif "LC_ALL" in os.environ.keys () and os.environ["LC_ALL"] != "":
  100.         env_lang = os.environ["LC_ALL"].split ()
  101.     elif "LC_MESSAGES" in os.environ.keys () and os.environ["LC_MESSAGES"] != "":
  102.         env_lang = os.environ["LC_MESSAGES"].split ()
  103.     elif "LANG" in os.environ.keys () and os.environ["LANG"] != "":
  104.         env_lang = os.environ["LANG"].split ()
  105.     else:
  106.         env_lang = []
  107.  
  108.     env_lang.reverse ()
  109.     languages = []
  110.  
  111.     for language in env_lang:
  112.         start_pos = 0
  113.         mask = 0
  114.         uscore_pos = language.find ("_")
  115.         if uscore_pos != -1:
  116.             start_pos = uscore_pos
  117.             mask += 1 << 2
  118.         dot_pos = language.find (".", start_pos)
  119.         if dot_pos != -1:
  120.             start_pos = dot_pos
  121.             mask += 1 << 0
  122.         at_pos = language.find ("@", start_pos)
  123.         if at_pos != -1:
  124.             start_pos = at_pos
  125.             mask += 1 << 1
  126.  
  127.         if uscore_pos != -1:
  128.             lang = language[:uscore_pos]
  129.         elif dot_pos != -1:
  130.             lang = language[:dot_pos]
  131.         elif at_pos != -1:
  132.             lang = language[:at_pos]
  133.         else:
  134.             lang = language
  135.  
  136.         if uscore_pos != -1:
  137.             if dot_pos != -1:
  138.                 territory = language[uscore_pos:dot_pos]
  139.             elif at_pos != -1:
  140.                 territory = language[uscore_pos:at_pos]
  141.             else:
  142.                 territory = language[uscore_pos:]
  143.         else:
  144.             territory = ""
  145.  
  146.         if dot_pos != -1:
  147.             if at_pos != -1:
  148.                 codeset = language[dot_pos:at_pos]
  149.             else:
  150.                 codeset = language[dot_pos:]
  151.         else:
  152.             codeset = ""
  153.  
  154.         if at_pos != -1:
  155.             modifier = language[at_pos:]
  156.         else:
  157.             modifier = ""
  158.  
  159.         for i in range (mask + 1):
  160.             if i & ~mask == 0:
  161.                 newlang = lang
  162.                 if (i & 1 << 2):
  163.                     newlang += territory
  164.                 if (i & 1 << 0):
  165.                     newlang += codeset
  166.                 if (i & 1 << 1):
  167.                     newlang += modifier
  168.                 languages.insert (0, newlang)
  169.  
  170.     return languages
  171.  
  172. class GettableList (list):
  173.     '''Dumb wrapper around Python list with a get () method to iterate \
  174. the list'''
  175.  
  176.     current = 0
  177.  
  178.     def get (self):
  179.         if not len (self):
  180.             return None
  181.         if self.current == -1:
  182.             item = None
  183.         else:
  184.             item = self[self.current]
  185.         self.current += 1
  186.         if self.current == len (self):
  187.             self.current = -1
  188.         else:
  189.             self.current = self.current % len (self)
  190.         return item
  191.  
  192. class GnomeContributors (GettableList):
  193.     '''Randomized contributors list'''
  194.  
  195.     current      = 0
  196.  
  197.     def __init__ (self):
  198.         '''Initiate object and load contributors lists'''
  199.         super (GnomeContributors, self).__init__ ()
  200.         map (self.append, translated_contributors)
  201.         self.load_from_file ("contributors.list")
  202.         self.load_from_file ("foundation-members.list")
  203.         random.shuffle (self) # Randomize list...
  204.  
  205.     def load_from_file (self, file):
  206.         '''Load a list of contributors and validate it'''
  207.         def validate_contributor (contrib):
  208.             try:
  209.                 contrib.encode ("utf8")
  210.                 return len (contrib) > 0 and contrib[0] != "#"
  211.             except Exception, e:
  212.                 print e
  213.                 return False
  214.  
  215.         path = locate_file (file)
  216.  
  217.         if not path:
  218.             print '''Warning: "%s" file not found.''' % file
  219.             return
  220.  
  221.         f = open (path, "r")
  222.         try:
  223.             data = f.readlines ()
  224.         finally:
  225.             f.close ()
  226.  
  227.         '''Cleanup list'''
  228.         data = map (lambda s: s.rstrip (), data)
  229.  
  230.         '''Check that the file begins with the correct header'''
  231.         if not data or data[0] != "# gnome-about contributors - format 1":
  232.             return
  233.  
  234.         '''Filter the contributors list and append it'''
  235.         contributors = filter (validate_contributor, data)
  236.         map (self.append, contributors)
  237.  
  238.     def get (self):
  239.         '''Return a contributor from the currently randomized list. \
  240.            If we hit the end of the list, randomize it again'''
  241.         contributors_count = len (self)
  242.         if not contributors_count:
  243.             print "Warning: empty contributors list."
  244.             return
  245.         contributor = self[self.current]
  246.         self.current += 1
  247.         if self.current >= contributors_count:
  248.             '''Remove the last item of the list and reinsert it after \
  249.                shuffling to make sure it won't get displayed twice in a row'''
  250.             self.pop ()
  251.             random.shuffle (self)
  252.             index = random.randint (contributors_count / 2,
  253.                                     contributors_count)
  254.             self.insert (index, contributor)
  255.             self.current = 0
  256.         return contributor
  257.  
  258. # Animation is done using:
  259. #  * A custom gtk.Label, for catching mouse press events
  260. #  * A gtk.Alignment, for positionning and scrolling (the actual animation)
  261. #  * A gtk.Layout, so that the Alignment can be wider than what's displayed
  262. #    and thus let the Label appear and disappear smoothly
  263.  
  264. class AnimatedLabel (gtk.Layout):
  265.     '''Pretty animated label'''
  266.  
  267.     items   = None # items must either be a GettableList object
  268.                    # or expose a get () method'''
  269.     timeout = 0
  270.     format  = ""   # format must be a valid formatting string for a single %s
  271.  
  272.     item    = None
  273.     next    = None
  274.  
  275.     current = None
  276.     label   = None
  277.     source  = None
  278.     state   = 0    # 0 = appearing ; 1 = landed ; 2 = vanishing
  279.     pos     = 0.0
  280.  
  281.     width   = 0
  282.     height  = 0
  283.  
  284.     def __init__ (self, items, width, height, timeout, format = "%s"):
  285.         '''Initiate object'''
  286.         super (AnimatedLabel, self).__init__ ()
  287.         self.items = items
  288.         self.next = self.items.get () # Pop the first item
  289.         self.width = width
  290.         self.height = height
  291.         self.timeout = timeout
  292.         self.format = format
  293.         self.set_size_request (width, height)
  294.         self.connect ("button-press-event", self.on_button_press)
  295.         self.connect ("map", self.reset_animation)
  296.  
  297.     def reset_animation (self, *args):
  298.         '''Reset label and fire animation timer'''
  299.         self.reset_label ()
  300.         if not self.label:
  301.             return
  302.         self.source = gobject.timeout_add (5, self.animate)
  303.  
  304.     def make_label (self):
  305.         '''Build the label widgets'''
  306.         self.label = WindowedLabel ()
  307.         self.label.connect ("button-press-event", self.on_button_press)
  308.         self.label.set_justify (gtk.JUSTIFY_FILL)
  309.         self.label.set_line_wrap (True)
  310.         self.label.set_markup (self.format % self.item)
  311.         self.label.set_selectable (True)
  312.  
  313.     def reset_label (self):
  314.         '''Drop current label if any and create the new one'''
  315.         if self.current:
  316.             self.remove (self.current)
  317.             self.current = None
  318.             self.label = None
  319.         self.item = self.next
  320.         self.next = self.items.get ()
  321.         if not self.item:
  322.             return False
  323.         self.make_label ()
  324.         self.state = -1
  325.  
  326.     def on_button_press (self, widget, event):
  327.         '''Switch to next item upon left click'''
  328.         if event.button != 1 or not self.current:
  329.             return
  330.         # Remove the current timeout if any to avoid bad side effects
  331.         if self.source:
  332.             gobject.source_remove (self.source)
  333.         self.animate ()
  334.         return True
  335.  
  336. class VertAnimatedLabel (AnimatedLabel):
  337.     '''Vertically animated label'''
  338.  
  339.     rewind_text       = ""
  340.     last_label_height = 0
  341.  
  342.     def rewind_animate (self):
  343.         '''Animation function for the rewind step'''
  344.         self.source = None
  345.         if self.state == -2:
  346.             self.item = self.rewind_text
  347.             self.make_label ()
  348.             label_height = self.label.size_request ()[1]
  349.             total_height = self.height + label_height
  350.             self.pos = float (self.last_label_height) / total_height
  351.             self.current.set (0.5, self.pos, 0, 0)
  352.             self.state = 0
  353.             self.source = gobject.timeout_add (10, self.rewind_animate)
  354.         elif self.state == 0:
  355.             if self.pos < 1.0:
  356.                 '''Move towards the bottom position'''
  357.                 self.pos = min (1.0, self.pos + 0.01)
  358.                 self.current.set (0.5, self.pos, 0, 0)
  359.                 self.source = gobject.timeout_add (10, self.rewind_animate)
  360.             else:
  361.                 '''Bottommost position reached'''
  362.                 self.rewind_text = ""
  363.                 self.reset_animation ()
  364.         return False
  365.  
  366.     def animate (self):
  367.         '''The actual animation function'''
  368.         self.source = None
  369.         if self.state == -2:
  370.             self.rewind_animate ()
  371.         elif self.state == -1:
  372.             self.rewind_text += "\n\n%s" % self.item
  373.             self.state = 0
  374.             self.animate ()
  375.         elif self.state == 0:
  376.             if self.pos:
  377.                 '''Move towards the top position'''
  378.                 self.pos = max (0, self.pos - 0.02)
  379.                 label_height = self.label.size_request ()[1]
  380.                 total_height = self.height + label_height
  381.                 real_pos = float (self.pos * self.height + label_height) \
  382.                             / total_height
  383.                 self.current.set (0.5, real_pos, 0, 0)
  384.                 self.source = gobject.timeout_add (5, self.animate)
  385.             else:
  386.                 '''Topmost position reached'''
  387.                 self.state = 1
  388.                 self.pos = 1.0
  389.                 self.source = gobject.timeout_add (self.timeout, self.animate)
  390.         elif self.state == 1:
  391.             '''Dont let selected labels vanish until they are unselected'''
  392.             if self.label.get_selection_bounds () == ():
  393.                 self.state = 2
  394.             self.source = gobject.timeout_add (5, self.animate)
  395.         elif self.state == 2:
  396.             if not self.next:
  397.                 self.state = -2
  398.                 self.last_label_height = self.label.size_request ()[1]
  399.                 self.reset_animation ()
  400.                 self.source = gobject.timeout_add (1, self.animate)
  401.             elif self.pos:
  402.                 '''Move out of the visible region of the Layout'''
  403.                 self.pos = max (0, self.pos - 0.02)
  404.                 label_height = self.label.size_request ()[1]
  405.                 total_height = self.height + label_height
  406.                 real_pos = float (self.pos * label_height) \
  407.                             / total_height
  408.                 self.current.set (0.5, real_pos, 0, 0)
  409.                 self.source = gobject.timeout_add (5, self.animate)
  410.             else:
  411.                 '''Label has disappeared, bye bye'''
  412.                 self.reset_animation ()
  413.         return False
  414.  
  415.     def make_label (self):
  416.         '''Build a new label widget'''
  417.         super (VertAnimatedLabel, self).make_label ()
  418.         if not self.label:
  419.             return
  420.         self.label.set_size_request (self.width, -1)
  421.         self.current = gtk.Alignment (0.0, 1.0)
  422.         label_height = self.label.size_request ()[1]
  423.         height = self.size_request ()[1]
  424.         self.current.set_size_request (-1, 2 * label_height + height)
  425.         self.current.add (self.label)
  426.         self.put (self.current, 0, - label_height)
  427.         self.pos = 1.0
  428.         self.show_all ()
  429.  
  430. class HorzAnimatedLabel (AnimatedLabel):
  431.     '''Horizontally animated label'''
  432.  
  433.     def animate (self):
  434.         '''The actual animation function'''
  435.         self.source = None
  436.         if self.state == -2:
  437.             self.reset_animation ()
  438.         elif self.state <= 0:
  439.             if self.pos != 0.5:
  440.                 '''Move towards the center position'''
  441.                 self.pos = max (0.5, self.pos - 0.02)
  442.                 self.current.set (self.pos, 0.5, 0, 0)
  443.                 self.source = gobject.timeout_add (5, self.animate)
  444.             else:
  445.                 '''Center position reached, switch to return mode'''
  446.                 self.state = 1
  447.                 self.source = gobject.timeout_add (self.timeout, self.animate)
  448.         elif self.state == 1:
  449.             '''Dont let selected labels vanish until they are unselected'''
  450.             if self.label.get_selection_bounds () == ():
  451.                 self.state = 2
  452.             self.source = gobject.timeout_add (5, self.animate)
  453.         elif self.state == 2:
  454.             if self.pos:
  455.                 '''Disappear by moving left'''
  456.                 self.pos = max (0, self.pos - 0.02)
  457.                 self.current.set (self.pos, 0.5, 0, 0)
  458.                 self.source = gobject.timeout_add (5, self.animate)
  459.             else:
  460.                 '''Left position reached, let's move on'''
  461.                 self.reset_animation ()
  462.         return False
  463.  
  464.     def make_label (self):
  465.         '''Build a new label widget'''
  466.         super (HorzAnimatedLabel, self).make_label ()
  467.         if not self.label:
  468.             return
  469.         self.label.set_size_request (-1, self.height)
  470.         self.current = gtk.Alignment (1.0, 0.0)
  471.         label_width = self.label.size_request ()[0]
  472.         width = self.size_request ()[0]
  473.         self.current.set_size_request (2 * label_width + width, -1)
  474.         self.current.add (self.label)
  475.         self.put (self.current, - label_width, 0)
  476.         self.pos = 1.0
  477.         self.show_all ()
  478.  
  479. class WindowedLabel (gtk.Label):
  480.     '''Custom gtk.Label with an overlapping input-only gtk.gdk.Window'''
  481.  
  482.     event_window = None
  483.  
  484.     def __init__ (self, debug = False):
  485.         '''Initialize object and plug all signals'''
  486.         self.debug = debug
  487.         super (WindowedLabel, self).__init__ ()
  488.  
  489.     def do_realize (self):
  490.         '''Create a custom GDK window with which we will be able to play'''
  491.         gtk.Label.do_realize (self)
  492.         event_mask = self.get_events () | gtk.gdk.BUTTON_PRESS_MASK \
  493.                                         | gtk.gdk.BUTTON_RELEASE_MASK \
  494.                                         | gtk.gdk.KEY_PRESS_MASK
  495.         self.event_window = gtk.gdk.Window (parent = self.get_parent_window (),
  496.                                             window_type = gtk.gdk.WINDOW_CHILD,
  497.                                             wclass = gtk.gdk.INPUT_ONLY,
  498.                                             event_mask = event_mask,
  499.                                             x = self.allocation.x,
  500.                                             y = self.allocation.y,
  501.                                             width = self.allocation.width,
  502.                                             height = self.allocation.height)
  503.         self.event_window.set_user_data (self)
  504.  
  505.     def do_unrealize (self):
  506.         '''Destroy event window on unrealize'''
  507.         self.event_window.set_user_data (None)
  508.         self.event_window.destroy ()
  509.         gtk.Label.do_unrealize (self)
  510.  
  511.     def do_size_allocate (self, allocation):
  512.         '''Move & resize the event window to fit the Label's one'''
  513.         gtk.Label.do_size_allocate (self, allocation)
  514.         if self.flags () & gtk.REALIZED:
  515.             self.event_window.move_resize (allocation.x, allocation.y,
  516.                                            allocation.width, allocation.height)
  517.  
  518.     def do_map (self):
  519.         '''Show event window'''
  520.         gtk.Label.do_map (self)
  521.         self.event_window.show ()
  522.         '''Raise the event window to make sure it is over the Label's one'''
  523.         self.event_window.raise_ ()
  524.  
  525.     def do_unmap (self):
  526.         '''Hide event window on unmap'''
  527.         self.event_window.hide ()
  528.         gtk.Label.do_unmap (self)
  529.  
  530. gobject.type_register (WindowedLabel)
  531.  
  532. class HyperLink (WindowedLabel):
  533.     '''Clickable www link label'''
  534.  
  535.     url       = ""
  536.     menu      = None
  537.     selection = None
  538.  
  539.     def __init__ (self, label, url):
  540.         '''Initialize object'''
  541.         super (HyperLink, self).__init__ ()
  542.         markup = "<b><u>%s</u></b>" % label
  543.         self.set_markup (markup)
  544.         self.set_selectable (True)
  545.         self.url = url
  546.         self.create_menu ()
  547.         link_color = self.style_get_property ("link-color") 
  548.         if not link_color:
  549.             link_color = default_link_color
  550.         self.modify_fg (gtk.STATE_NORMAL, link_color)
  551.  
  552.     def open_url (self, *args):
  553.         '''Use GNOME API to open the url'''
  554.         try:
  555.             gtk.show_uri (self.get_screen(), self.url, 0)
  556.         except Exception, e:
  557.             print '''Warning: could not open "%s": %s''' % (self.url, e)
  558.  
  559.     def copy_url (self, *args):
  560.         '''Copy URL to Clipboard'''
  561.         clipboard = gtk.clipboard_get ("CLIPBOARD")
  562.         clipboard.set_text (self.url)
  563.  
  564.     def create_menu (self):
  565.         '''Create the popup menu that will be displayed upon right click'''
  566.         self.menu = gtk.Menu ()
  567.         open_item = gtk.ImageMenuItem (_("_Open URL"))
  568.         open_image = gtk.image_new_from_stock (gtk.STOCK_OPEN,
  569.                                                gtk.ICON_SIZE_MENU)
  570.         open_item.set_image (open_image)
  571.         open_item.connect ("activate", self.open_url)
  572.         open_item.show ()
  573.         self.menu.append (open_item)
  574.         copy_item = gtk.ImageMenuItem (_("_Copy URL"))
  575.         copy_image = gtk.image_new_from_stock (gtk.STOCK_COPY,
  576.                                                gtk.ICON_SIZE_MENU)
  577.         copy_item.set_image (copy_image)
  578.         copy_item.connect ("activate", self.copy_url)
  579.         copy_item.show ()
  580.         self.menu.append (copy_item)
  581.  
  582.     def display_menu (self, button, time, place = False):
  583.         '''Display utility popup menu'''
  584.         if place:
  585.             alloc = self.get_allocation ()
  586.             pos = self.event_window.get_origin ()
  587.             x = pos[0]
  588.             y = pos[1] + alloc.height
  589.             func = lambda *a: (x, y, True)
  590.         else:
  591.             func = None
  592.         self.menu.popup (None, None, func, button, time)
  593.  
  594.     def do_map (self):
  595.         '''Select the HAND2 cursor on map'''
  596.         WindowedLabel.do_map (self)
  597.         cursor = gtk.gdk.Cursor (gtk.gdk.HAND2)
  598.         self.event_window.set_cursor (cursor)
  599.  
  600.     def do_button_press_event (self, event):
  601.         '''Update selection bounds infos or display popup menu'''
  602.         if event.button == 1:
  603.             self.selection = self.get_selection_bounds ()
  604.         elif event.button == 3:
  605.             self.display_menu (event.button, event.time)
  606.             return True
  607.         WindowedLabel.do_button_press_event (self, event)
  608.  
  609.     def do_button_release_event (self, event):
  610.         '''Open url if selection hasn't changed since initial press'''
  611.         if event.button == 1:
  612.             selection = self.get_selection_bounds ()
  613.             if selection == self.selection:
  614.                 self.open_url ()
  615.                 return True
  616.         WindowedLabel.do_button_release_event (self, event)
  617.  
  618.     def do_key_press_event (self, event):
  619.         '''Open url when Return key is pressed'''
  620.         if event.keyval == gtk.keysyms.Return:
  621.             self.open_url ()
  622.             return True
  623.         elif event.keyval == gtk.keysyms.Menu \
  624.           or (event.keyval == gtk.keysyms.F10 \
  625.               and event.state & gtk.accelerator_get_default_mod_mask() == \
  626.                   gtk.gdk.SHIFT_MASK):
  627.             self.display_menu (event.keyval, event.time, place = True)
  628.             return True
  629.         WindowedLabel.do_key_press_event (self, event)
  630.  
  631. gobject.type_register (HyperLink)
  632.  
  633. class GnomeLogo (gtk.Widget):
  634.     '''Simple widget displaying a colored GNOME logo'''
  635.  
  636.     _surface = None
  637.     _parent  = None
  638.     _file    = None
  639.  
  640.     def __init__ (self, parent, file):
  641.         '''Initialize object and do the initial painting'''
  642.         gtk.Widget.__init__ (self)
  643.  
  644.         self._parent = parent
  645.         self._file = file
  646.  
  647.         self.paint_surface ()
  648.  
  649.         width = self._surface.get_width ()
  650.         height = self._surface.get_height ()
  651.         self.set_size_request (width, height)
  652.  
  653.         self.set_flags(self.flags() | gtk.NO_WINDOW)
  654.  
  655.     def paint_surface (self):
  656.         '''Paint image and color overlay'''
  657.         self._surface = cairo.ImageSurface.create_from_png (self._file)
  658.  
  659.         text_color = self._parent.get_style ().fg[gtk.STATE_NORMAL]
  660.  
  661.         cr = cairo.Context (self._surface)
  662.         cr.set_source_rgb (float (text_color.red) / 65535,
  663.                            float (text_color.green) / 65535,
  664.                            float (text_color.blue) / 65535)
  665.         cr.set_operator (cairo.OPERATOR_ATOP)
  666.         cr.paint ()
  667.  
  668.     def do_expose_event (self, event):
  669.         '''Paint the saved surface to the widget context'''
  670.         cr = self.window.cairo_create ()
  671.  
  672.         cr.set_operator (cairo.OPERATOR_OVER)
  673.         cr.set_source_surface (self._surface, 10, 10)
  674.         cr.paint ()
  675.  
  676.     def do_style_set (self, *args):
  677.         '''Style changed, let's repaint image'''
  678.         self.paint_surface ()
  679.         self.queue_draw ()
  680.  
  681. gobject.type_register (GnomeLogo)
  682.  
  683. class GnomeAboutHeader (gtk.Layout):
  684.     '''Pretty header for gnome-about'''
  685.  
  686.     links   = []
  687.  
  688.     width   = 0
  689.     height  = 0
  690.  
  691.     def __init__ (self, links):
  692.         '''Initialize object, plug map signal'''
  693.         super (GnomeAboutHeader, self).__init__ () 
  694.         self.links = links
  695.  
  696.     def do_realize (self):
  697.         '''Load header and build links'''
  698.         gtk.Layout.do_realize (self)
  699.  
  700.         current_x = 0
  701.         current_y = 0
  702.         base_y = 0
  703.  
  704.         header = self.load_header ()
  705.         if header:
  706.             self.put (header, 0, 0)
  707.             current_y = header.get_pixbuf ().get_height ()
  708.             base_y = current_y + 4
  709.             line = self.create_line ()
  710.             image = gtk.Image ()
  711.             image.set_from_pixmap (line, None)
  712.             self.put (image, 0, current_y)
  713.  
  714.         logo = self.load_logo ()
  715.         if logo:
  716.             self.put (logo, 0, 0)
  717.             logo_size = logo.get_size_request ()
  718.             current_x += logo_size[0] + 25
  719.             current_y = logo_size[1] + 20
  720.  
  721.         dot = self.create_dot ()
  722.  
  723.         def make_link_widget (link):
  724.             '''Helper function which makes an HyperLink and shows it'''
  725.             label = HyperLink (link[0], link[1])
  726.             label.show_all ()
  727.             return label
  728.  
  729.         widgets = map (make_link_widget, self.links)
  730.         put_widgets = 0
  731.         for widget in widgets:
  732.             if put_widgets > 0:
  733.                 if dot:
  734.                     image = gtk.Image ()
  735.                     image.set_from_pixmap (dot, None)
  736.                     self.put (image, current_x + 5, base_y + 6)
  737.                 current_x += 16
  738.             self.put (widget, current_x, base_y)
  739.             current_x += widget.size_request ()[0]
  740.             put_widgets += 1
  741.  
  742.         self.width = current_x + 10
  743.         self.height = current_y
  744.         self.set_size_request (self.width, self.height)
  745.         self.show_all ()
  746.  
  747.     def load_header (self):
  748.         '''Load a random header image as a gtk.Image'''
  749.  
  750.         directory = locate_file ("headers")
  751.         if not directory:
  752.             print "Warning: header images directory not found."
  753.             return None
  754.  
  755.         try:
  756.             headers = os.listdir (directory)
  757.         except:
  758.             print "Warning: failed to read header images directory."
  759.             return None
  760.  
  761.         headers = filter (lambda s: s[-4:] in (".png", ".gif"), headers)
  762.         header = random.choice (headers)
  763.  
  764.         file = os.path.join (directory, header)
  765.         try:
  766.             pixbuf = gtk.gdk.pixbuf_new_from_file (file)
  767.         except:
  768.             print '''Warning: failed to load header image "%s".''' % file
  769.             return None
  770.  
  771.         image = gtk.Image ()
  772.         image.set_from_pixbuf (pixbuf)
  773.  
  774.         return image
  775.  
  776.     def load_logo (self):
  777.         '''Load a GNOME foot logo as a gtk.Image'''
  778.  
  779.         file = locate_file (LOGO_FILE)
  780.         if not file:
  781.             print '''Warning: GNOME logo file "%s" not found.''' % LOGO_FILE
  782.             return None
  783.  
  784.         try:
  785.             logo = GnomeLogo (self, file)
  786.         except Exception:
  787.             print '''Warning: failed to load GNOME logo image "%s".''' % file
  788.             return None
  789.  
  790.         return logo
  791.  
  792.     def create_dot (self):
  793.         '''Create a pixmap containing a simple dot'''
  794.         pixmap = gtk.gdk.Pixmap (self.window, 6, 6)
  795.         context = pixmap.cairo_create ()
  796.         context.set_operator (cairo.OPERATOR_SOURCE)
  797.         context.set_source_color (self.style.bg[self.state])
  798.         context.paint ()
  799.         context.set_operator (cairo.OPERATOR_OVER)
  800.         context.set_source_color (self.style.fg[self.state])
  801.         context.arc (3, 3, 2.3, 0, 2 * pi)
  802.         context.fill ()
  803.         return pixmap
  804.  
  805.     def create_line (self, width = 2000, height = 1):
  806.         '''Create a pixmap containing a simple line'''
  807.         pixmap = gtk.gdk.Pixmap (self.window, width, height)
  808.         context = pixmap.cairo_create ()
  809.         context.set_operator (cairo.OPERATOR_SOURCE)
  810.         context.set_source_rgb (0, 0, 0)
  811.         context.paint ()
  812.         return pixmap
  813.  
  814. gobject.type_register (GnomeAboutHeader)
  815.  
  816. class GnomeAbout (gtk.Dialog):
  817.     '''Super pretty About Dialog for the GNOME Desktop'''
  818.  
  819.     header               = None
  820.     contributors         = None
  821.     description_messages = GettableList ()
  822.     system_infos         = []
  823.  
  824.     def __init__ (self, ui = True):
  825.         '''Initialize underlying gnome.Program, Contributors list, UI...'''
  826.         super (GnomeAbout, self).__init__ (_("About the GNOME Desktop"),
  827.                                            buttons = (gtk.STOCK_CLOSE,
  828.                                                       gtk.RESPONSE_CLOSE))
  829.  
  830.         # Immediately fetch system infos to load description messages
  831.         self.system_infos = self.get_system_infos ()
  832.  
  833.         if not ui:
  834.             return
  835.  
  836.         self.contributors = GnomeContributors ()
  837.  
  838.         icon_file = ICONDIR + "/gnome-logo-icon-transparent.png"
  839.         try:
  840.             self.set_icon_from_file (icon_file)
  841.         except gobject.GError:
  842.             pass
  843.  
  844.         self.create_ui ()
  845.  
  846.         self.set_default_response (gtk.RESPONSE_CLOSE)
  847.         self.set_position (gtk.WIN_POS_CENTER)
  848.         map (lambda prop: self.set_property (prop[0], prop[1]),
  849.              [("allow-grow", False), ("allow-shrink", False)])
  850.         self.connect ("delete-event", gtk.main_quit)
  851.         self.connect ("response", self.response_callback)
  852.  
  853.     def gnome_version (self):
  854.         '''Output basic GNOME version information to console'''
  855.         def print_info (info):
  856.             infos_dict = {"name": info[0], "value": info[1]}
  857.             # Translators: %(name)s and %(value)s should not be translated:
  858.             # it's a way to identify a string, so just handle them like %s
  859.             print _("%(name)s: %(value)s") % infos_dict
  860.         map (print_info, self.system_infos)
  861.  
  862.     def create_ui (self):
  863.         '''Fill our Dialog with some lovely widgets'''
  864.         self.set_has_separator (False)
  865.         main_box = self.get_children ()[0] # Get the internal Dialog VBox
  866.  
  867.         '''Pretty header'''
  868.         self.header = GnomeAboutHeader (header_links)
  869.         main_box.pack_start (self.header)
  870.  
  871.         welcome_label = WindowedLabel ()
  872.         welcome_label.set_markup ("<big><big><b>%s</b></big></big>" % \
  873.                                   _("Welcome to the GNOME Desktop"))
  874.         main_box.pack_start (welcome_label)
  875.  
  876.         descriptions_label = VertAnimatedLabel (self.description_messages,
  877.                                                 300, 120,
  878.                                                 DESCRIPTION_DELAY, "%s")
  879.         welcome_label.connect ("button-press-event",
  880.                                descriptions_label.on_button_press)
  881.         box = gtk.EventBox ()
  882.         alignment = gtk.Alignment (0.5, 0.5)
  883.         alignment.set_padding (10, 10, 0, 0)
  884.         alignment.add (descriptions_label)
  885.         box.connect ("button-press-event", descriptions_label.on_button_press)
  886.         box.add (alignment)
  887.         main_box.pack_start (box)
  888.  
  889.         by_label = WindowedLabel (True)
  890.         by_label.set_markup ("<big><b>%s</b></big>" % _("Brought to you by:"))
  891.         main_box.pack_start (by_label)
  892.  
  893.         alignment = gtk.Alignment (0.5, 0.5)
  894.         '''Realize the header right now to get everything (especially the
  895. contributors list) correctly positionned and sized.'''
  896.         self.header.realize ()
  897.         width = self.header.width
  898.         label = HorzAnimatedLabel (self.contributors, width,
  899.                                    30, CONTRIBUTOR_DELAY, "<b>%s</b>")
  900.         by_label.connect ("button-press-event", label.on_button_press)
  901.         label.show_all ()
  902.         alignment.add (label)
  903.  
  904.         main_box.pack_start (alignment)
  905.  
  906.         '''System info labels'''
  907.         def make_info_label (info):
  908.             if not info[1]:
  909.                 return False
  910.             label = gtk.Label ()
  911.             infos_dict = {"name": info[0], "value": info[1]}
  912.             # Translators: %(name)s and %(value)s should not be translated:
  913.             # it's a way to identify a string, so just handle them like %s
  914.             label.set_markup (_("<b>%(name)s:</b> %(value)s") % infos_dict)
  915.             label.set_selectable (True)
  916.             label.connect ("focus-out-event",
  917.                            lambda l, e: l.select_region (-1, -1))
  918.             alignment = gtk.Alignment (0, 0.5)
  919.             alignment.set_padding (0, 4, 8, 0)
  920.             alignment.add (label)
  921.             return alignment
  922.  
  923.         info_labels = map (make_info_label, self.system_infos)
  924.         info_labels = filter (lambda l: l <> False, info_labels)
  925.         map (lambda l: main_box.pack_start (l, False, False), info_labels)
  926.  
  927.         main_box.show_all ()
  928.  
  929.     def get_system_infos (self):
  930.         '''Fetch various system infos'''
  931.         file = locate_file ("gnome-version.xml")
  932.         if not file:
  933.             print '''Warning: "gnome-version.xml" file not found.'''
  934.             return []
  935.  
  936.         f = open (file, "r")
  937.         try:
  938.             document = xml.dom.minidom.parse (f)
  939.         finally:
  940.             f.close ()
  941.  
  942.         if document.firstChild.nodeName != "gnome-version":
  943.             print '''Warning: corrupted "gnome-version.xml".'''
  944.             return []
  945.  
  946.         infos = {
  947.                     "platform" : "",
  948.                     "minor" : "",
  949.                     "micro" : "",
  950.                     "distributor" : "",
  951.                     "date" : ""
  952.                 }
  953.  
  954.         for node in document.firstChild.childNodes:
  955.             if node.nodeName in infos:
  956.                 infos[node.nodeName] = node.firstChild.nodeValue
  957.             elif node.nodeName == "description":
  958.                 self.load_description_messages (node)
  959.  
  960.         '''Format version'''
  961.         if not len (infos["platform"]):
  962.             version = ""
  963.         elif not len (infos["minor"]):
  964.             version = infos["platform"]
  965.         elif not len (infos["micro"]):
  966.             version = "%s.%s" % (infos["platform"], infos["minor"])
  967.         else:
  968.             version = "%s.%s.%s" % (infos["platform"], infos["minor"],
  969.                                     infos["micro"])
  970.  
  971.         date = cleanup_date (infos["date"])
  972.  
  973.         retval = []
  974.         if version:
  975.             retval.append((_("Version"), version))
  976.         if infos["distributor"]:
  977.             retval.append((_("Distributor"), infos["distributor"]))
  978.         if date:
  979.             retval.append((_("Build Date"), date))
  980.  
  981.         return retval
  982.  
  983.     def load_description_messages (self, node):
  984.         '''Find the best translation of each description message'''
  985.         languages = get_language_names () + [""]
  986.         def desc_filter_func (node):
  987.             '''Helper filter function for XML descriptions'''
  988.             return node.nodeName == "p" \
  989.                and node.getAttribute ("xml:lang") in languages
  990.         def desc_sort_func (a, b):
  991.             '''Helper sorting function to sort translated messages'''
  992.             return cmp (languages.index (a), languages.index (b))
  993.         descs = filter (desc_filter_func, node.childNodes)
  994.         raw_descs = filter (lambda n: n.getAttribute ("xml:lang") == "", descs)
  995.         i = -1
  996.         translations = []
  997.         for desc in raw_descs:
  998.             new_i = descs.index (desc)
  999.             if i != - 1:
  1000.                 # Append the previous description
  1001.                 translations.append (descs[i:new_i])
  1002.             i = new_i
  1003.         # Open ended to retrieve all translation for last description
  1004.         translations.append (descs[i:])
  1005.         messages = GettableList ()
  1006.         for block in translations:
  1007.             sorted_descs = sorted (block, cmp = desc_sort_func,
  1008.                                    key = lambda n: n.getAttribute ("xml:lang"))
  1009.             best = sorted_descs[0].firstChild.nodeValue
  1010.             messages.append (best)
  1011.         self.description_messages = messages
  1012.  
  1013.     def response_callback (self, widget, response):
  1014.         '''Handle dialog response when Close button is triggered'''
  1015.         if response == gtk.RESPONSE_CLOSE:
  1016.             gtk.main_quit ()
  1017.  
  1018. if __name__ == "__main__":
  1019.     parser = OptionParser (
  1020.             option_list = [
  1021.                     make_option ("--gnome-version",
  1022.                                  action="store_true",
  1023.                                  dest="gnome_version",
  1024.                                  help=_("Display information on this GNOME version")),
  1025.             # FIXME: remove this when we can get all the default
  1026.             # options
  1027.                     make_option ("--version",
  1028.                                  action="store_true",
  1029.                                  dest="version",
  1030.                                  help=VERSION),
  1031.                 ])
  1032.     #FIXME: http://bugzilla.gnome.org/show_bug.cgi?id=496278
  1033.     parser.parse_args (sys.argv)
  1034.     if parser.values.gnome_version:
  1035.         about = GnomeAbout (ui = False)
  1036.         about.gnome_version ()
  1037.     elif parser.values.version:
  1038.         print "GNOME gnome-about %s" % (VERSION)
  1039.     else:
  1040.         about = GnomeAbout ()
  1041.         about.show_all ()
  1042.         try:
  1043.             gtk.main ()
  1044.         except KeyboardInterrupt:
  1045.             pass
  1046.